package val.to.hardware.location;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import val.to.config.Constants;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;

public class LocationProvider{	
	
	public interface ILocationListener{
		void onLocationReady(Location location);
		void onTimeout();
		void onError();
	}
	
	private boolean mIsRunning;
	private Timer mTimeoutTimer = null;   
    private LocationManager mLocationManager = null;
    private CustomLocationListener listener = null;

    private ArrayList<ILocationListener> mListeners = new ArrayList<ILocationListener>();
    
    public LocationProvider(Context ctx){	
    	mTimeoutTimer = new Timer();
		mLocationManager = (LocationManager)ctx.getSystemService(Context.LOCATION_SERVICE);
		listener = new CustomLocationListener();
    }
    
    public void addListener(ILocationListener listner){
    	synchronized (mListeners) {
        	mListeners.add(listner);		
		}
    }

	public void start(){	
		
		if (mIsRunning){
			Log.d(Constants.TAG, "LocationProvider is active already.");
			return;
		}
		
		mIsRunning = true;
		mTimeoutTimer.cancel();
		mTimeoutTimer = new Timer();

	    for (String s : mLocationManager.getAllProviders()) {
	        mLocationManager.requestLocationUpdates(s,
	        		Constants.Hardware.LOCATION_POLL_INTERVAL, 
	        		Constants.Hardware.MIN_DISTANCE, 
	        		listener);
	    }
	    
	    mTimeoutTimer.schedule(new TimerTask() {
			@Override
	        public void run() {
	        	onTimeout();
			}
		},  Constants.Hardware.LOCATION_TIMEOUT);
	}

	public void stop() {
		if (!mIsRunning){
			Log.d(Constants.TAG, "LocationProvider is not active already.");
			return;
		}

		mIsRunning = false;
		mTimeoutTimer.cancel();
		mLocationManager.removeUpdates(listener);
	}
	
	private void onTimeout() {
	    stop();
	    
	    synchronized (mListeners) {
	    	for(ILocationListener listener : mListeners){
	    		listener.onTimeout();
	    	}
		}
	}

	private void doLocationUpdate(Location location) {	    
	    stop();
	    
	    if (location == null) {
		    synchronized (mListeners) {
		    	for(ILocationListener listener : mListeners){
		    		listener.onError();
		    	}
			}
	    }
	    else{
	    	synchronized (mListeners) {
		    	for(ILocationListener listener : mListeners){
		    		listener.onLocationReady(location);
		    	}
			}
	    }
	}	
	
	Location getBestLocation() {
	    Location gpslocation = getLocationByProvider(LocationManager.GPS_PROVIDER);
	    Location networkLocation = getLocationByProvider(LocationManager.NETWORK_PROVIDER);

	    // if we have only one location available, the choice is easy
	    if (gpslocation == null) {
	        Log.d(Constants.TAG, "LocationService: No GPS Location available.");
	        return networkLocation;
	    }
	    if (networkLocation == null) {
	    	Log.d(Constants.TAG, "LocationService: No Network Location available.");
	        return gpslocation;
	    }

	    // a location update is considered 'old' if its older than the configured update interval. this means, 
	    // we didn't get an update from this provider since the last check
	    long old = System.currentTimeMillis() - Constants.Hardware.LOCATION_POLL_INTERVAL;
	    boolean gpsIsOld = (gpslocation.getTime() < old);
	    boolean networkIsOld = (networkLocation.getTime() < old);

	    //gps is current and available, gps is better than network
	    if (!gpsIsOld) {
	    	Log.d(Constants.TAG, "LocationService: Using GPS");
	        return gpslocation;
	    }

	    //gps is old, we can't trust it. use network location
	    if (!networkIsOld) {
	    	Log.d(Constants.TAG, "LocationService: GPS is old, using Network");
	        return networkLocation;
	    }

	    // both are old return the newer of those two
	    if (gpslocation.getTime() > networkLocation.getTime()) {
	    	Log.d(Constants.TAG, "LocationService: Both are old, returning gps (newer)");
	        return gpslocation;
	    } 
	    
	    Log.d(Constants.TAG, "LocationService: Both are old, returning network(newer)");
        return networkLocation;
	}

	private Location getLocationByProvider(String provider) {
	    Location location = null;
	    if (!isProviderSupported(provider)) {
	        return null;
	    }	    

	    try {
	        if (mLocationManager.isProviderEnabled(provider)) {
	            location = mLocationManager.getLastKnownLocation(provider);
	        }
	    } catch (IllegalArgumentException e) {
	    	Log.d(Constants.TAG, "LocationService: Cannot acces " + provider);
	    }
	    return location;
	}
	
	public boolean isProviderSupported(String  provider) {        
        final List<String> providers = mLocationManager.getAllProviders();
        return providers.contains(provider);        
    }
	
	private class CustomLocationListener implements LocationListener {
		public CustomLocationListener() {
        	// Not used
		}

		@Override
		public void onStatusChanged(String provider, int status, Bundle extras) {
			// TODO Not used for now					
		}
		
		@Override
		public void onProviderEnabled(String provider) {
			// TODO Not used for now					
		}
		
		@Override
		public void onProviderDisabled(String provider) {
			// TODO Not used for now					
		}
		
		@Override
		public void onLocationChanged(Location location) {
			// if this is a gps location, we can use it
            //if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
        	//	Log.i(Constants.TAG, "LocationService: GPS Location change detected");
            //    doLocationUpdate(location);
            //}
			doLocationUpdate(location);
		}
	}
}
